package com.imooc.pan.server.common.schedule.task;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.imooc.pan.core.constants.RPanConstants;
import com.imooc.pan.pan.schedule.ScheduleTask;
import com.imooc.pan.server.common.event.log.ErrorLogEvent;
import com.imooc.pan.server.modules.file.entity.RPanFileChunk;
import com.imooc.pan.server.modules.file.service.RPanFileChunkService;
import com.imooc.pan.storage.engine.core.StorageEngine;
import com.imooc.pan.storage.engine.core.context.DeleteFileContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 过期分片清理任务
 */
@Component
@Slf4j
public class CleanExpireChunkFileTask implements ScheduleTask, ApplicationContextAware {

    private static  final Long BATCH_SIZE = 500L;
    @Autowired
    private RPanFileChunkService fileChunkService;
    @Autowired
    private StorageEngine storageEngine;
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 获取定时任务名称
     * @return
     */
    @Override
    public String getName() {
        return "CleanExpireChunkFileTask";
    }

    /**
     * 执行清理任务
     * 1.滚动查询过期的文件分片
     * 2.删除物理文件(委托文件存储引擎去做)
     * 3.删除过期文件分片的记录信息
     * 4.重置上次查询的最大文件分片记录ID,继续滚动查询
     */
    @Override
    public void run() {
        log.info("{},start clean expire chunk file...",getName());
        List<RPanFileChunk> expiredFileChunkRecords;
        Long scrollPointer = 1L;
        do{
            expiredFileChunkRecords = scrollQueryExpireFileChunkRecords(scrollPointer);
            if(CollectionUtils.isNotEmpty(expiredFileChunkRecords)){
                deleteRealChunkFiles(expiredFileChunkRecords);
                List<Long> idList = deleteChunkFIleRechords(expiredFileChunkRecords);
                scrollPointer = Collections.max(idList);
            }
        }while (CollectionUtils.isNotEmpty(expiredFileChunkRecords));

    }



    /**
     * 滚动查询过期的文件分片记录
     * @param scrollPointer
     * @return
     */
    private List<RPanFileChunk> scrollQueryExpireFileChunkRecords(Long scrollPointer) {
        QueryWrapper queryWrapper = Wrappers.query();
        queryWrapper.le("expiration_time",new Date());
        queryWrapper.ge("id", scrollPointer);
        queryWrapper.last(" limit " + BATCH_SIZE);
        return fileChunkService.list(queryWrapper);
    }

    /**
     * 物理删除过期的文件分片文件实体
     * @param expiredFileChunkRecords
     */
    private void deleteRealChunkFiles(List<RPanFileChunk> expiredFileChunkRecords) {
        DeleteFileContext deleteContext = new DeleteFileContext();
        List<String> realPaths = expiredFileChunkRecords.stream().map(RPanFileChunk::getRealPath).collect(Collectors.toList());
        deleteContext.setRealFilePathList(realPaths);
        try{
            storageEngine.delete(deleteContext);
        }catch (IOException e){
            saveErrorLog(realPaths);
        }

    }

    /**
     * 发布删除失败事件
     * @param realPaths
     */
    private void saveErrorLog(List<String> realPaths) {
        ErrorLogEvent event = new ErrorLogEvent(this, "文件物理删除失败，请手动执行文件删除！文件路径为：" + JSON.toJSONString(realPaths), RPanConstants.ZERO_LONG);
        applicationContext.publishEvent(event);
    }

    /**
     * 删除过期文件分片记录
     */
    private List<Long> deleteChunkFIleRechords(List<RPanFileChunk> expiredFileChunkRecords) {
        List<Long> idList = expiredFileChunkRecords.stream().map(RPanFileChunk::getId).collect(Collectors.toList());
        fileChunkService.removeByIds(idList);
        return idList;
    }
}
